﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LunarPhase.Model
{
    public class PhaseCalculator
    {
        private const double PI = 3.1415926535897932385;

        public PhaseCalculatorResult Calculate(DateTime date)
        {
            int year = date.Year;
            int month = date.Month;
            int day = date.Day;

            //  put "Moon on ", month, '/', day, '/', year
            return MoonPosition(year, month, day);
        }

        //%  compute moon position and phase
        private PhaseCalculatorResult MoonPosition(int year, int month, int day)
        {
            // Moon's age
            double moonAge;

            // Moon's distance in earth radii
            double moonDistance;

            // Moon's ecliptic latitude
            double moonEclipticLatitude;

            // Moon's ecliptic longitude
            double moonEclipticLongitude;
            LunarPhase Phase;
            Zodiac zodiac;
            int YY;
            int MM;
            int K1;
            int K2;
            int K3;
            int JD;
            double IP;
            double DP;
            double NP;
            double RP;

            //% calculate the Julian date at 12h UT
            YY = year - (int)System.Math.Floor((double)((12 - month) / 10));
            MM = month + 9;
            if (MM >= 12)
            {
                MM = MM - 12;
            }

            K1 = (int)Math.Floor(365.25 * (YY + 4712));
            K2 = (int)Math.Floor(30.6 * MM + 0.5);
            K3 = (int)Math.Floor(Math.Floor((double)((YY / 100) + 49)) * 0.75) - 38;

            //% for dates in Julian calendar
            JD = K1 + K2 + day + 59; 
            if (JD > 2299160)
            {
                //% for Gregorian calendar
                JD = JD - K3; 
            }

            //% calculate moon's age in days
            IP = Normalize((JD - 2451550.1) / Moon.MaxMoonAge);
            moonAge = IP * Moon.MaxMoonAge;

            Phase = DeterminatePhase(moonAge);

            //% Convert phase to radians
            IP = IP * 2 * PI; 
            
            // % calculate moon's distance
            DP = 2 * PI * Normalize((JD - 2451562.2) / 27.55454988);
            moonDistance = 60.4 - 3.3 * Math.Cos(DP) - 0.6 * Math.Cos(2 * IP - DP) - 0.5 * Math.Cos(2 * IP);

            // % calculate moon's ecliptic latitude
            NP = 2 * PI * Normalize((JD - 2451565.2) / 27.212220817);
            moonEclipticLatitude = 5.1 * Math.Sin(NP);

            // % calculate moon's ecliptic longitude
            RP = Normalize((JD - 2451555.8) / 27.321582241);
            moonEclipticLongitude = 360 * RP + 6.3 * Math.Sin(DP) + 1.3 * Math.Sin(2 * IP - DP) + 0.7 * Math.Sin(2 * IP);

            zodiac = DeterminateZodiac(moonEclipticLongitude);

            return CreatePhaseCalculaterResult(moonAge, moonDistance, moonEclipticLatitude, moonEclipticLongitude, Phase, zodiac);

            // % display results
            //put "phase         = ", Phase
            //put "age           = ", round2( moonAge ), " days"
            //put "distance      = ", round2( moonDistance ), " earth radii"
            //put "ecliptic"
            //put " latitude     = ", round2( LA ), '°'
            // put " longitude    = ", round2( LO ), '°'
            //put "constellation = ", Zodiac

        }

        private PhaseCalculatorResult CreatePhaseCalculaterResult(double moonAge, double moonDistance, double moonEclipticLatitude, double moonEclipticLongitude, LunarPhase Phase, Zodiac zodiac)
        {
            PhaseCalculatorResult calculatorResult = new PhaseCalculatorResult();
            calculatorResult.Phase = Phase;
            calculatorResult.AgeDays = Round2(moonAge);
            
            calculatorResult.Distance = Round2(moonDistance);
            
            calculatorResult.Latitude = Round2(moonEclipticLatitude);
            
            calculatorResult.Longitude = Round2(moonEclipticLongitude); 
            calculatorResult.Sign = zodiac;
            return calculatorResult;
        }

        private Zodiac DeterminateZodiac(double LO)
        {
            Zodiac zodiac;
            if (LO < 33.18) { zodiac = Zodiac.Pisces; }
            else if (LO < 51.16) { zodiac = Zodiac.Aries; }
            else if (LO < 93.44) { zodiac = Zodiac.Taurus; }
            else if (LO < 119.48) { zodiac = Zodiac.Gemini; }
            else if (LO < 135.30) { zodiac = Zodiac.Cancer; }
            else if (LO < 173.34) { zodiac = Zodiac.Leo; }
            else if (LO < 224.17) { zodiac = Zodiac.Virgo; }
            else if (LO < 242.57) { zodiac = Zodiac.Libra; }
            else if (LO < 271.26) { zodiac = Zodiac.Scorpio; }
            else if (LO < 302.49) { zodiac = Zodiac.Sagittarius; }
            else if (LO < 311.72) { zodiac = Zodiac.Capricorn; }
            else if (LO < 348.58) { zodiac = Zodiac.Aquarius; }
            else
            {
                zodiac = Zodiac.Pisces;
            }
            return zodiac;
        }

        private LunarPhase DeterminatePhase(double moonAge)
        {
            LunarPhase Phase;
            if (moonAge < 1.84566)
            {
                Phase = LunarPhase.NEW;
            }
            else if (moonAge < 5.53699)
            {
                Phase = LunarPhase.WaxingCrescent;
            }
            else if (moonAge < 9.22831)
            {
                Phase = LunarPhase.FirstQuarter;
            }
            else if (moonAge < 12.91963)
            {
                Phase = LunarPhase.WaxingGibbous;
            }
            else if (moonAge < 16.61096)
            {
                Phase = LunarPhase.FULL;
            }
            else if (moonAge < 20.30228)
            {
                Phase = LunarPhase.WaningGibbous;
            }

            else if (moonAge < 23.99361)
            {
                Phase = LunarPhase.LastQuarter;
            }
            else if (moonAge < 27.68493)
            {
                Phase = LunarPhase.WaningCrescent;
            }
            else
            {
                Phase = LunarPhase.NEW;
            }
            return Phase;
        }

        // % round to 2 decimal places
        private double Round2(double x)
        {
            return (Math.Round(100 * x) / 100.0);
        }

        // % normalize values to range 0...1
        private double Normalize(double v)
        {
            v = v - Math.Floor(v);
            //if (v < 0)
            //{
            //    v = v + 1;
            //}

            return v;
        }

    }
}
